﻿using Gym.Application.Common.Interfaces;
using Gym.Domain.SessionAggregate;
using Microsoft.EntityFrameworkCore;

namespace Gym.Infrastructure.Persistence.Repositories
{
    public class SessionsRepository : ISessionsRepository
    {
        private readonly GymDbContext _dbContext;
        public SessionsRepository(GymDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task AddSessionAsync(Session session)
        {
            await _dbContext.Sessions.AddAsync(session);
            await _dbContext.SaveChangesAsync();
        }

        public async Task<Session?> GetByIdAsync(Guid id)
        {
            return await _dbContext.Sessions.FirstOrDefaultAsync(s => s.Id == id);
        }

        public async Task<List<Session>> ListByGymIdAsync(
            Guid gymId,
            DateTime? startDateTime = null,
            DateTime? endDateTime = null,
            List<SessionCategory>? categories = null)
        {
            var gymRooms = await _dbContext.Rooms
            .AsNoTracking()
            .Where(room => room.GymId == gymId)
            .ToListAsync();

            var sessionIds = gymRooms.SelectMany(room => room.SessionIds).ToList();

            return await ListByIdsAsync(sessionIds, startDateTime, endDateTime, categories);
        }

        public async Task<List<Session>> ListByIdsAsync(
            IReadOnlyList<Guid> sessionIds,
            DateTime? startDateTime = null,
            DateTime? endDateTime = null,
            List<SessionCategory>? categories = null)
        {
            return await _dbContext.Sessions
            .AsNoTracking()
            .Where(session => sessionIds.Contains(session.Id))
            .WhereBetweenDateAndTimes(startDateTime, endDateTime)
            .WhereOfCategory(categories)
            .ToListAsync();
        }

        public async Task<List<Session>> ListByRoomIdAsync(Guid roomId)
        {
            return await _dbContext.Sessions
            .Where(session => session.RoomId == roomId)
            .ToListAsync();
        }

        public async Task RemoveRangeAsync(List<Session> sessions)
        {
            _dbContext.RemoveRange(sessions);
            await _dbContext.SaveChangesAsync();
        }

        public async Task UpdateAsync(Session session)
        {
            _dbContext.Update(session);
            await _dbContext.SaveChangesAsync();
        }
    }

    file static class DbContextSessionExtensions
    {
        public static IQueryable<Session> WhereBetweenDateAndTimes(this IQueryable<Session> query, DateTime? start, DateTime? end)
        {
            if (start == null && end == null)
            {
                return query;
            }

            start ??= DateTime.MinValue;
            end ??= DateTime.MaxValue;

            var startDate = DateOnly.FromDateTime(start.Value);
            var endDate = DateOnly.FromDateTime(end.Value);

            var startTime = TimeOnly.FromDateTime(start.Value);
            var endTime = TimeOnly.FromDateTime(end.Value);

            // 如果日期相同，则直接比较时间
            if (startDate == endDate)
            {
                return query
                    .Where(session => session.Date == startDate)
                    .Where(session => session.Time.Start >= startTime && session.Time.Start <= endTime);
            }
            else
            {
                return query.Where(session =>
                    // 开始日期的session，时间必须>=开始时间
                    (session.Date == startDate && session.Time.Start >= startTime) ||
                    // 中间日期的所有session都可以
                    (session.Date > startDate && session.Date < endDate) ||
                    // 结束日期的session，时间必须<=结束时间
                    (session.Date == endDate && session.Time.Start <= endTime)
                );
            }
        }

        public static IQueryable<Session> WhereOfCategory(this IQueryable<Session> query, List<SessionCategory>? categories)
        {
            if (categories is null || categories.Count == 0)
            {
                return query;
            }

            var categoryNames = categories.ConvertAll(category => category.Name);

            return query
                .AsNoTracking()
                .Where(session => session.Categories.Any(category => categoryNames.Contains(category.Name)));
        }
    }
}
